using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
using DP.Sharepoint.Workflow.Tests.ForTesting;

namespace DP.Sharepoint.Workflow.Tests.ForTesting
{
    public sealed class WorkflowRuntimeManager : IDisposable
    {
        private readonly WorkflowRuntime _workflowRuntime;

        private readonly Dictionary<Guid, WorkflowInstanceWrapper> _workflows =
            new Dictionary<Guid, WorkflowInstanceWrapper>();

        private Int32 _waitTime = 5000;
        public Queue<MyEvent> EventsToRaise = new Queue<MyEvent>();

        /// <summary>
        /// Contructor
        /// </summary>
        /// <param name="instance"></param>
        public WorkflowRuntimeManager(WorkflowRuntime instance)
        {
            if (instance == null)
                throw new NullReferenceException("A non-null WorkflowRuntime instance is required");
            _workflowRuntime = instance;

            SubscribeToEvents(instance);
        }

        public Int32 IntervalWaitTime
        {
            get { return _waitTime; }
            set { _waitTime = value; }
        }

        public WorkflowRuntime WorkflowRuntime
        {
            get { return _workflowRuntime; }
        }

        public Dictionary<Guid, WorkflowInstanceWrapper> Workflows
        {
            get { return _workflows; }
        }

        ~WorkflowRuntimeManager()
        {
            Dispose(false);
        }

        public Guid CreateWorkflow(Type workflowType, Dictionary<String, Object> parameters)
        {
            Guid workflowId = AddWorkflowInstance(
                _workflowRuntime.CreateWorkflow(workflowType, parameters)
                );
            return workflowId;
        }


        public Guid CreateStartWorkflow(Type workflowType, Dictionary<String, Object> parameters)
        {
            Guid workflowId = CreateWorkflow(workflowType, parameters);

            return RunWorkflow(workflowId, IntervalWaitTime);
        }

        //public WorkflowInstanceWrapper CreateStartWorkflow(XmlReader workflowDefiniationReader, XmlReader rulesReader,
        //                                             Dictionary<String, Object> parameters)
        //{
        //    WorkflowInstance instance = _workflowRuntime.CreateWorkflow(workflowDefiniationReader, rulesReader,
        //                                                                parameters);
        //    WorkflowInstanceWrapper wrapper = AddWorkflowInstance(instance);
        //    instance.Start();

        //    return RunWorkflow(wrapper);
        //}

        public Guid RunWorkflow(Guid workflowId, Int32 timeout)
        {
            Trace.WriteLine("WorkflowRuntimeManager.RunWorkflow");
            WorkflowInstanceWrapper wrapper = FindWrapper(workflowId);
            wrapper.WorkflowInstance.Start();

            ManualWorkflowSchedulerService scheduler = WorkflowRuntime.GetService<ManualWorkflowSchedulerService>();
            if (scheduler != null)
            {
                scheduler.RunWorkflow(workflowId);
            }

            WaitAll(timeout);

            return workflowId;
        }

        public event EventHandler<WorkflowLogEventArgs> MessageEvent;

        #region Collection management

        public void ClearWorkflow(Guid workflowId)
        {
            if (_workflows.ContainsKey(workflowId))
            {
                _workflows.Remove(workflowId);
            }
        }

        public void ClearAllWorkflows()
        {
            if (_workflows != null)
            {
                _workflows.Clear();
            }
        }

        private Guid AddWorkflowInstance(WorkflowInstance instance)
        {
            if (Workflows.ContainsKey(instance.InstanceId))
            {
                throw new Exception("workflow already in workflows collection");
            }

            WorkflowInstanceWrapper wrapper = new WorkflowInstanceWrapper(instance);
            _workflows.Add(wrapper.Id, wrapper);

            return instance.InstanceId;
        }

        public WorkflowInstanceWrapper FindWrapper(Guid workflowId)
        {
            WorkflowInstanceWrapper result = null;
            if (_workflows.ContainsKey(workflowId))
            {
                result = _workflows[workflowId];
            }
            return result;
        }

        public void WaitAll(Int32 msecondsTimeout)
        {
            if (_workflows.Count > 0)
            {
                WaitHandle[] handles = new WaitHandle[_workflows.Count];
                Int32 index = 0;
                foreach (WorkflowInstanceWrapper wrapper in _workflows.Values)
                {
                    handles[index] = wrapper.WaitHandle;
                    index++;
                }
                WaitHandle.WaitAll(handles, msecondsTimeout, false);
            }
        }

        #endregion

        #region IDisposable members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void Dispose(bool disposing)
        {
            ClearAllWorkflows();

            if (_workflowRuntime != null)
            {
                _workflowRuntime.StopRuntime();
                _workflowRuntime.Dispose();
            }
        }

        #endregion

        #region Workflow Event Handling

        private void LogStatus(Guid instanceId, String msg)
        {
            if (MessageEvent != null)
            {
                string formattedMsg = instanceId == Guid.Empty
                                          ? String.Format("Runtime - {0}", msg)
                                          : String.Format("{0} - {1}", instanceId, msg);

                MessageEvent(this, new WorkflowLogEventArgs(formattedMsg));
            }
        }

        public void SubscribeToEvents(WorkflowRuntime runtime)
        {
            runtime.Started += delegate { LogStatus(Guid.Empty, "Started"); };
            runtime.Stopped += delegate { LogStatus(Guid.Empty, "Stopped"); };
            runtime.WorkflowAborted += runtime_WorkflowAborted;
            runtime.WorkflowCompleted += runtime_WorkflowCompleted;
            runtime.WorkflowCreated +=
                delegate(object sender, WorkflowEventArgs e) { LogStatus(e.WorkflowInstance.InstanceId, "Created"); };
            runtime.WorkflowIdled += runtime_WorkflowIdled;
            runtime.WorkflowLoaded +=
                delegate(object sender, WorkflowEventArgs e) { LogStatus(e.WorkflowInstance.InstanceId, "Loaded"); };
            runtime.WorkflowPersisted +=
                delegate(object sender, WorkflowEventArgs e) { LogStatus(e.WorkflowInstance.InstanceId, "Persisted"); };
            runtime.WorkflowResumed +=
                delegate(object sender, WorkflowEventArgs e) { LogStatus(e.WorkflowInstance.InstanceId, "Resumed"); };
            runtime.WorkflowStarted +=
                delegate(object sender, WorkflowEventArgs e) { LogStatus(e.WorkflowInstance.InstanceId, "Started"); };
            runtime.WorkflowSuspended += runtime_WorkflowSuspended;
            runtime.WorkflowTerminated += runtime_WorkflowTerminated;
            runtime.WorkflowUnloaded +=
                delegate(object sender, WorkflowEventArgs e) { LogStatus(e.WorkflowInstance.InstanceId, "Unloaded"); };
        }

        private void runtime_WorkflowIdled(object sender, WorkflowEventArgs e)
        {
            LogStatus(e.WorkflowInstance.InstanceId, "Idled");

            ReadOnlyCollection<WorkflowQueueInfo> queueInfoData = e.WorkflowInstance.GetWorkflowQueueData();

            foreach (WorkflowQueueInfo info in queueInfoData)
            {
                EventQueueName eventQueue = info.QueueName as EventQueueName;
                if (eventQueue == null)
                {
                    break;
                }

                Trace.Write(string.Format("runtime_WorkflowIdled | {0}", eventQueue.MethodName));
            }
        }


        private void runtime_WorkflowAborted(object sender, WorkflowEventArgs e)
        {
            LogStatus(Guid.Empty, "WorkflowAborted");
            WorkflowInstanceWrapper wrapper = FindWrapper(e.WorkflowInstance.InstanceId);
            if (wrapper != null)
            {
                wrapper.StopWaiting();
            }
        }

        private void runtime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
        {
            LogStatus(e.WorkflowInstance.InstanceId, "WorkflowTerminated");
            WorkflowInstanceWrapper wrapper = FindWrapper(e.WorkflowInstance.InstanceId);
            if (wrapper != null)
            {
                wrapper.Exception = e.Exception;
                wrapper.StopWaiting();
            }
        }

        private void runtime_WorkflowSuspended(object sender, WorkflowSuspendedEventArgs e)
        {
            LogStatus(e.WorkflowInstance.InstanceId, "WorkflowSuspended");
            WorkflowInstanceWrapper wrapper = FindWrapper(e.WorkflowInstance.InstanceId);
            if (wrapper != null)
            {
                wrapper.ReasonSuspended = e.Error;
                //TODO: review Not sure here why suspended don't need to StopWaiting...
                //wrapper.StopWaiting();
            }
        }


        private void runtime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
        {
            LogStatus(e.WorkflowInstance.InstanceId, "WorkflowCompleted");
            WorkflowInstanceWrapper wrapper = FindWrapper(e.WorkflowInstance.InstanceId);
            if (wrapper != null)
            {
                wrapper.OutputParameters = e.OutputParameters;
                wrapper.StopWaiting();
            }
        }

        #endregion

        //private void RaiseWorkflowActivatedEvent(object stateInfo)
        //{
        //    Trace.WriteLine("\nHost:  Loading workflow due to event firing...");

        //    MyEvent evt = stateInfo as MyEvent;
        //    if (evt == null)
        //        throw new InvalidOperationException("function called with invalid information should be [MyEvent]");

        //    if (evt.EventToRaise == null)
        //        throw new InvalidOperationException("EventToRaise was not set.");

        //    evt.EventToRaise.Fire(null, evt.Arguments);
        //}

        #region Nested type: MyEvent

        public class MyEvent
        {
            private readonly EventArgs _args;
            private readonly Guid _instanceId;
            private readonly String _name;
            private readonly Type _serviceType;

            public MyEvent()
            {
            }

            public MyEvent(Guid instanceid, Type t, String Name, EventArgs args)
            {
                _instanceId = instanceid;
                _serviceType = t;
                _name = Name;
                _args = args;
            }

            public EventArgs Arguments
            {
                get { return _args; }
            }

            public string EventName
            {
                get { return _name; }
            }

            public Type serviceType
            {
                get { return _serviceType; }
            }

            public Guid InstanceId
            {
                get { return _instanceId; }
            }
        }

        #endregion
    }

    public class WorkflowLogEventArgs : EventArgs
    {
        private readonly String _msg = String.Empty;

        public WorkflowLogEventArgs(String msg)
        {
            _msg = msg;
        }

        public String Message
        {
            get { return _msg; }
        }
    }
}